/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/task.c,v 1.32 2003/03/25 09:39:16 bboy Exp $

	Copyright (C) 2002, 2003  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"

extern void server_status(void);
extern void named_cleanup(int);


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	DESCTASK
	Describe a task; used by error/warning/debug output.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *
desctask(TASK *t)
{
	static char desc[1024];

	snprintf(desc, sizeof(desc), "[%lu] %s: %s %s",
		t->internal_id,
		inet_ntoa(t->addr.sin_addr),
		mydns_qtype_str(t->qtype), t->qname ? (char *)t->qname : "<NONE>");
	return (desc);
}
/*--- desctask() --------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	TASK_INIT
	Allocates and initializes a new task, and returns a pointer to it.
	t = task_init(NEED_ZONE, SOCK_DGRAM, fd, &addr);
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
TASK *
task_init(
	taskstat_t status,			/* Initial status */
	int fd,							/* Associated file descriptor for socket */
	int protocol,					/* Protocol (SOCK_DGRAM or SOCK_STREAM) */
	struct sockaddr_in *addr	/* Remote address */
)
{
	TASK *new;

	if (!(new = calloc(1, sizeof(TASK))))
		Err("calloc");
	new->status = status;
	new->fd = fd;
	new->protocol = protocol;
	memcpy(&new->addr, addr, sizeof(struct sockaddr_in));
	new->internal_id = Status.udp_requests + Status.tcp_requests;
	if (answer_then_quit && new->internal_id > answer_then_quit)
	{
		server_status();
		named_cleanup(SIGQUIT);
	}
	new->timeout = current_time + task_timeout;
	new->minimum_ttl = DNS_MINIMUM_TTL;
	new->reply_cache_ok = 1;

	return (new);
}
/*--- task_init() -------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	TASK_FREE
	Free the memory used by a task.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
task_free(TASK *t)
{
	register int n;

	if (!t)
		return;

	memset(&t->Cnames, 0, sizeof(t->Cnames));
	if (t->query)
		free(t->query);
#if DYNAMIC_NAMES
	for (n = 0; n < t->numNames; n++)
		free(t->Names[n]);
	if (t->numNames)
		free(t->Names);
	free(t->Offsets);
#endif

	free(t->qd);
	t->qd = NULL;
	rrlist_free(&t->an);
	rrlist_free(&t->ns);
	rrlist_free(&t->ar);
	if (t->rdata)
		free(t->rdata);
	if (t->reply)
		free(t->reply);
	free(t);
}
/*--- task_free() -------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	TASK_INIT_HEADER
	Sets and/or clears header fields and values as necessary.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
task_init_header(TASK *t)
{
	t->hdr.qr = 1;						/* This is the response, not the query */
	t->hdr.ra = 0;						/* Recursion is NOT available (this may change in the future) */
	t->hdr.rcode = DNS_R_NOERROR;	/* Assume success unless told otherwise */
}
/*--- task_init_header() ------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	TASK_OUTPUT_INFO
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
task_output_info(TASK *t)
{
	struct timeval tv;
	time_t tt;
	struct tm *tm;
	char datebuf[80];

	/* Don't output anything for TCP sockets in the process of closing */
	if (t->protocol == SOCK_STREAM && t->fd < 0)
		return;

	gettimeofday(&tv, NULL);
	tt = tv.tv_sec;
	tm = localtime(&tt);

	strftime(datebuf, sizeof(datebuf)-1, "%d-%b-%Y %H:%M:%S", tm);

	Verbose(
		"%s+%06lu "
		"#%lu "
		"%d "		/* Client-provided ID */
		"%s "		/* TCP or UDP? */
		"%s "		/* Client IP */
		"%s "		/* Class */
		"%s "		/* Query type (A, MX, etc) */
		"%s " 	/* Name */
		"%s "		/* Return code (NOERROR, NXDOMAIN, etc) */
		"%s "		/* Reason */
		"%d "		/* Question section */
		"%d "		/* Answer section */
		"%d "		/* Authority section */
		"%d"		/* Additional section */
		,
		datebuf, tv.tv_usec,
		t->internal_id,
		t->id,
		t->protocol == SOCK_STREAM ? "TCP" : "UDP",
		inet_ntoa(t->addr.sin_addr),
		mydns_class_str(t->qclass),
		mydns_qtype_str(t->qtype),
		t->qname,
		mydns_rcode_str(t->hdr.rcode),
		err_reason_str(t->reason),
		t->qdcount,
		t->an.size,
		t->ns.size,
		t->ar.size
	);
}
/*--- task_output_info() ------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	TASK_PROCESS
	Process the specified task, if possible.  Returns a pointer to the next task.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
TASK *
task_process(TASK *t)
{
	TASK *next;

	next = t->next;

	/* Process task if necessary */
	if (t->status == NEED_READ)
	{
		switch (t->protocol)
		{
			case SOCK_DGRAM:
				Warnx("%s: %s", desctask(t), _("invalid state for UDP query"));
				dequeue(Tasks, t, 1);
				return (next);

			case SOCK_STREAM:
				if (read_tcp_query(t) < 0)
				{
#if HAVE_SHUTDOWN
					shutdown(t->fd, 2);
#endif
					close(t->fd);
					t->fd = -1;
					dequeue(Tasks, t, 1);
					return (next);
				}
				break;

			default:
				Warnx("%s: %d: %s", desctask(t), t->protocol, _("unknown/unsupported protocol"));
				dequeue(Tasks, t, 1);
				return (next);
		}
	}

	if (t->status == NEED_ANSWER)
	{
		if (t->protocol != SOCK_STREAM && reply_from_cache(t))
		{
			char *dest = t->reply;

			DNS_PUT16(dest, t->id);								/* Query ID */
			DNS_PUT(dest, &t->hdr, SIZE16);					/* Header */
			t->status = NEED_WRITE;
		}
		else
		{
			resolve(t, ANSWER, t->qtype, t->qname, 0);
			build_reply(t, 1);
			if (t->reply_cache_ok)
				add_reply_to_cache(t);
			t->status = NEED_WRITE;
			if (t->protocol == SOCK_STREAM)
				return (next);
		}
	}

	if (t->status == NEED_WRITE)
	{
		switch (t->protocol)
		{
			case SOCK_DGRAM:
				write_udp_reply(t);
				break;

			case SOCK_STREAM:
				if (write_tcp_reply(t) < 0)
				{
					dequeue(Tasks, t, 1);
					return (next);
				}
				break;

			default:
				Warnx("%s: %d: %s", desctask(t), t->protocol, _("unknown/unsupported protocol"));
				dequeue(Tasks, t, 1);
				return (next);
		}
	}

	return (next);
}
/*--- task_process() ----------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
